package EP::Visualization::ClusteringComparisonGraphic;

use strict;

use EP::Config;
use EP::Visualization::Hooks;
use EP::Visualization::Graphics;

use PDL;
use PDL::NiceSlice;

our @ISA = qw ( EP::Visualization::Graphics );

sub setData {
  my $self = shift;
  my %params = @_;

#  die "No heatmap matrix supplied to ClusteringComparisonGraphic::setData" if not defined $params{data};

  $self->{cell_height} = $params{cell_height} if $params{cell_height};
  $self->{width}       = $params{width} || 200;

  $self->{tree} = $params{tree};
  $self->{heatmap} = $params{heatmap};
  $self->{comparison} = $params{comparison};

  $self->{interactive} = $params{interactive} if     defined $params{interactive};
  $self->{interactive} = 0                    if not defined $params{interactive};
}

sub getWidth {
  my $self = shift;
  return $self->{width};
}

sub getHeight {
  my $self = shift;
  my $data = $self->{heatmap}->getData();

  my ( $cols, $rows ) = $data->dims();

  return $rows * $self->{cell_height};
}

#####################################################
#returns string identifying the object as a heatmap #
#####################################################

sub getType{
  return "clusteringcomparison";
}

####################################################
#Set the vertical hooks (corresponds to the rows)  #
####################################################
sub setVerticalHookSet{
  my $self = shift;
  my $hook= shift;
  $self->{verticalHook} = $hook;
  return;
}

####################################################
#get the vertical hooks (corresponds to the rows)  #
####################################################
sub getVerticalHookSet{
  my $self = shift;
  return $self->{verticalHook};
}

#########################################################
#set the horizontal hooks (corresponds to the columns)  #
#########################################################
sub setHorizontalHookSet{
  my $self = shift;
  my $hook= shift;
  $self->{horizontalHook} = $hook;
  return;
}

#########################################################
#get the horizontal hooks (corresponds to the columns)  #
#########################################################
sub getHorizontalHookSet {
  my $self = shift;
  return $self->{horizontalHook};
}

##########################################################
#add the heatmap svg elements to the svg initiated in    #
#the Grid object                                         #
##########################################################
sub to_svg {
  my $self = shift;
  my ( $svg, $height, $width, $xpos, $ypos ) = @_;

  my $group = $svg->group ( transform => "translate($xpos,$ypos)" );  #translates the heatmap according to it's position in the grid

  my $superclusters_matrix  = transpose ( $self->{comparison}->get('superclusters_matrix') );
  my $ordered_weight_matrix = transpose ( $self->{comparison}->get('ordered_weight_matrix') );
  my $cluster_order         = $self->{comparison}->get('cluster_order');

  my @colors = qw ( red green orange blue purple thistle black fuchsia white yellow );
  my @q;

  # FAIRLY CLUMSY NUMBER OF ELEMENTS COMPUTATIONS FOLLOW. NEED AURORA TO REWRITE THESE SENSIBLY!
  my $scls = $superclusters_matrix->dim(0) - 1;
  foreach my $i ( 0 .. $scls ) {
    my $w = which ( $superclusters_matrix ( $i,: ) == 1 );

    my $color;
    foreach ( list $w ) {
      $color = $q[$_] and last if exists $q[$_];
    }
    $color = pop @colors if not $color;

    if (not $color) {
      die "Ran out of colors for superclusters (10 maximum)!";
    }

    foreach ( list $w ) {
      $q[$_] = $color;
    }
  }

  my @c = list sumover ( $ordered_weight_matrix );
  my @cc = list sumover ( transpose ( $ordered_weight_matrix ) );

  my %sss;
  $sss{$q[$_]} += $c[$_] foreach 0 .. $#c;

  my $lastY = 1;
  foreach my $i ( 0 .. $#c ) {
    $group->rectangle ( 'x' => 3,
			'y' => $lastY,
			width => 13,
			height => $self->{cell_height} * $c[$i],
			fill => $q[$i],
			stroke => 'blue',
			'stroke-width' => 1.5,
			onmouseover => "ShowTooltip('$sss{$q[$i]} elements in supercluster', evt)",
			onmouseout => "HideTooltip()"
		      );

    foreach my $f ( list $cluster_order(which $ordered_weight_matrix(:,$i) != 0) ) {
      my ($cl) = grep { $cluster_order($_) == $f } 0 .. ( $cluster_order->dim(0) - 1 );
      my $elts = $ordered_weight_matrix->at ( $cl, $i );

      $group->circle ( cx => 25, cy => $lastY + 0.5 * $self->{cell_height} * $c[$i], r => 5, fill => 'orange', stroke=>'none',
		       onmouseover => "ShowTooltip('$c[$i] elements', evt)",
		       onmouseout => "HideTooltip()" );
      $group->line (
		    'x1' => 30,
		    'y1' => $lastY + 0.5 * $self->{cell_height} * $c[$i],
		    'x2' => $width - 15,
		    'y2' => $cl * $height / ( $cluster_order->dim(0) ) + 0.5 * $height / ( $cluster_order->dim(0) ),
		    stroke => 'orange',
		    'stroke-linecap' => 'round',
		    'stroke-width' => $elts > 15 ? 15 : $elts,
		    onmouseover => "ShowVennDiagram($c[$i],$elts,$cc[$cl], evt)",
		    onmouseout  => "HideVennDiagram(evt)"
		   );
    }

    $lastY += $self->{cell_height} * $c[$i];
  }

  my $m = $self->{comparison}->get('merging_matrix');
  my $mm = $m($cluster_order - 1);
  my $cls = $m->dim(0) - 1;
  foreach my $i ( 0 .. $cls ) {
    my $sc = which ( $mm(:,$i) == 1 );
    foreach ( list $sc ) {
      $group->circle ( cx => $width - 7,
		       cy => $_ * $height / ( $cluster_order->dim(0) ) + 0.5 * $height / ( $cluster_order->dim(0) ),
		       r => 5, fill => 'green',
		       stroke => 'none' );
    }


    next if $sc->dim(0) == 1;
    my $path = "M" . ($width-7) . " " . ( $sc->at(0) * $height / ( $cluster_order->dim(0) ) + 0.5 * $height / ( $cluster_order->dim(0) ) );

    my @l = list $sc;
    shift @l;

    foreach (@l) {
      $path .= " L" . ($width-7) . " " . ( $_ * $height / ( $cluster_order->dim(0) ) + 0.5 * $height / ( $cluster_order->dim(0) ) );
    }

    $group->path ( d => $path, stroke => 'green', 'stroke-width' => 5 );
  }

  my $g = $svg->group ( id => "vdg", display => "none" );
  $g->circle ( cx => 0, cy => 0, r => 20, fill => "slateblue", 'fill-opacity' => 0.3 );
  $g->circle ( cx => 25, cy => 0, r => 20, fill => "red", 'fill-opacity' => 0.3 );
  $g->text ( id => "vd_lc", x => -5, 'y' => 5, stroke => 'black', 'font-size' => '6', 'stroke-width' => 0.5 )->cdata ( ' ' );
  $g->text ( id => "vd_rc", x => 25, 'y' => 5, stroke => 'black', 'font-size' => '6', 'stroke-width' => 0.5 )->cdata ( ' ' );
  $g->text ( id => "vd_int", x => 10, 'y' => 5, stroke => 'black','font-size' => '6', 'stroke-width' => 0.5 )->cdata ( ' ' );

  return $svg;
}

sub draw_png_heatmap {
  my $self = shift;
  my ( $data, $width, $height, $rect_w, $rect_h ) = @_;
  my ( $cols, $rows ) = $data->dims();

  my $png = new File::Temp ( DIR => "$EP::Config::h->{TMP}",
			     UNLINK => 0,
			     SUFFIX => '.png' );

  # HACK: it seems PGPLOT has a hard-coded limit of 1000 for image width. If image is needed bigger than 1000, we'll need to scale it.
  $width = 1000 if $width > 1000;

  my $win = PDL::Graphics::PGPLOT::Window->new ( Dev=>"$png/tpng", Size => [ $width, $height, 3], Axis => -2, CharSize => 0 );
  $win->env ( -0.5, $cols - 0.5, $rows - 0.5, -0.5, {Axis => -2, CharSize => 0} );

  my $l = pdl 0, 0.5, 1;
  my $r = pdl 1, 0, 0;
  my $g = pdl 0, 0, 1;
  my $b = pdl 0, 0, 0;

  my ($mean,$rms,$median) = ($data->stats())[0,1,2];

  $win->ctab ( $l, $g, $r, $b );
  $win->imag ( $data, {Range => [$median-$rms*3,$median+$rms*3], Axis => -2, CharSize => 0} );
  $win->close ();

#  my $img = new GD::Image ( $width, $height );

#  my @colors = ( $img->colorAllocate ( 0, 0, 255 ) );
#  die "Failed to allocate colors" if $colors[0] == -1;

#  # allocate greens
#  foreach ( 1 .. 254 ) {
#    $colors[$_] = $img->colorAllocate ( 0, $_, 0 ) if $_ < 128;  # greens
#    $colors[$_] = $img->colorAllocate ( $_, 0, 0 ) if $_ >= 128; # reds

#    die "Failed to allocate colors" if $colors[$_] == -1;
#  }

#  my $isgood = $data->isgood;

#  for my $r ( 0 .. ($rows - 1) ) {
#    foreach my $c ( 0 .. ($cols - 1) ) {
#      my $rgb_color = $colors[0];

#      if ( $isgood->at($c,$r) ) {
#	my $val = $data->at($c,$r);

#	my $intensity = int ( 127 * abs ( $val ) ) % 127;
#	$rgb_color = ( $val < 0 ) ? $colors [ $intensity + 1 ] : $colors [ $intensity + 128 ];

#	die "GD ran out of colors" if $rgb_color == -1;	
#      }

#      $img->filledRectangle ( $c * $rect_w,
#			      $r * $rect_h,
#			      ( $c + 1 ) * $rect_w,
#			      ( $r + 1 ) * $rect_h,
#			      $rgb_color
#			    );
#    }
#  }

#  my $png = new File::Temp ( DIR => "$EP::Config::h->{TMP}",
#			     UNLINK => 0,
#			     SUFFIX => '.png' );
#  print $png $img->png;
#  close $png;

  warn "Generated heatmap into PNG ($width x $height): $png" if DEBUG;
  my ($png_filename) = $png->filename() =~ /$EP::Config::h->{TMP}\/(.*)/;

  return $png_filename;
}

###########################################################
#set the graphics object by which the columns of the data #
#should be ordered to                                     #
###########################################################

sub orderHorizontalBy{
  my $self = shift;
  my $graphicsObj = shift;
  $self -> { orderHorizontalBy } = $graphicsObj;
  return;
}

###########################################################
#set the graphics object by which the rows of the data    #
#should be ordered to                                     #
###########################################################

sub orderVerticalBy{
  my $self = shift;
  my $graphicsObj = shift;
  $self -> { orderVerticalBy } = $graphicsObj;
  return;
}

###################################################################
#order the datamatrix according to the graphics objects set above #
###################################################################
sub order {
  my $self = shift;

  my ( $col_order, $row_order );
  my $data = $self->getData();

  # Horizontal ordering: columns of the heatmap
  if ( exists $self->{orderHorizontalBy} ) {
    my $graphicsObj = $self->{orderHorizontalBy};

    my @orderedHook = @{$graphicsObj->getHook()->getHookSet()};
    if ( $graphicsObj -> getOrientation() == 180){
      @orderedHook = reverse @orderedHook;
    }

    $col_order = long @orderedHook;

    my $hook = new EP::Visualization::Hooks( \@orderedHook );
    $self->setHorizontalHookSet( $hook );
  }

  # Vertical ordering: rows of the heatmap
  if ( exists $self->{orderVerticalBy} ) {
    my $graphicsObj = $self->{orderVerticalBy};

    my @orderedHook = @{ $graphicsObj -> getHook -> getHookSet() };
    if ( $graphicsObj -> getOrientation() == -90 ){
      @orderedHook = reverse @orderedHook;
    }

    $row_order = long @orderedHook;

    my $hook = new EP::Visualization::Hooks( \@orderedHook );
    $self -> setVerticalHookSet( $hook );
  }

  my $ordered_data;

  $ordered_data = $data($col_order,$row_order) if defined $col_order and defined $row_order;
  $ordered_data = $data(,$row_order) if not defined $col_order and defined $row_order;
  $ordered_data = $data($col_order,) if defined $col_order and not defined $row_order;
  $ordered_data = $data if not defined $col_order and not defined $row_order;

  $self->setData ( data => $ordered_data, cell_height => $self->{cell_height}, cell_width => $self->{cell_width}, interactive => $self->{interactive} );
}

1;
